package com.jthinker.blog.dao.impl;

import com.jthinker.blog.dao.BaseDao;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import javax.persistence.Id;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * User: Gang Chen
 * Date: 2015/6/14 22:29
 */
public abstract class BaseDaoImpl<M extends Serializable, PK extends Serializable> implements BaseDao<M, PK>
{
    private static final String SORT_ORDER_DESC = "desc";

    private static final String SORT_ORDER_ASC = "asc";

    private static final String DEFAULT_SORT_ORDER = SORT_ORDER_DESC;

    private final Class<M> entityClass;
    private final String pkName;

    private final String HQL_LIST_ALL;
    private final String HQL_COUNT_ALL;
    private final String HQL_OPTIMIZE_PRE_LIST_ALL;
    private final String HQL_OPTIMIZE_NEXT_LIST_ALL;

    @Autowired
    private SessionFactory sessionFactory;

    public BaseDaoImpl()
    {
        this.entityClass = (Class<M>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        Field[] fields = this.entityClass.getDeclaredFields();

        String _pkName = null;
        for (Field f : fields)
        {
            if (f.isAnnotationPresent(Id.class))
            {
                _pkName = f.getName();
            }
        }
        this.pkName = _pkName;

        Assert.notNull(pkName);
        HQL_LIST_ALL = "from " + this.entityClass.getSimpleName();
        HQL_OPTIMIZE_PRE_LIST_ALL = "from " + this.entityClass.getSimpleName() + " where " + pkName + " >:" + pkName + " ";
        HQL_OPTIMIZE_NEXT_LIST_ALL = "from " + this.entityClass.getSimpleName() + " where " + pkName + " <:" + pkName + " ";
        HQL_COUNT_ALL = " select count(*) from " + this.entityClass.getSimpleName();
    }

    @Override
    public PK save(M model)
    {
        return (PK) getSession().save(model);
    }

    @Override
    public void saveOrUpdate(M model)
    {
        getSession().saveOrUpdate(model);
    }

    @Override
    public void update(M model)
    {
        getSession().update(model);
    }

    @Override
    public void merge(M model)
    {
        getSession().merge(model);
    }

    @Override
    public void delete(PK id)
    {
        getSession().delete(get(id));
    }

    @Override
    public void deleteObject(M model)
    {
        getSession().delete(model);
    }

    @Override
    public M get(PK id)
    {
        return (M) getSession().get(this.entityClass, id);
    }

    @Override
    public int countAll()
    {
        Object obj = getSession().createQuery(HQL_COUNT_ALL).uniqueResult();
        return obj != null ? ((Long) obj).intValue() : 0;
    }

    @Override
    public List<M> listAll()
    {
        return list(HQL_LIST_ALL, -1, -1);
    }

    @Override
    public List<M> listAll(int pn, int pageSize)
    {
        return list(HQL_LIST_ALL, pn, pageSize);
    }

    @Override
    public List<M> pre(PK pk, int pn, int pageSize)
    {
        if (pk == null)
        {
            return listAll(pn, pageSize);
        }
        Map<String, Object> params = new HashMap<String, Object>();
        params.put(pkName, pk);
        return list(HQL_OPTIMIZE_PRE_LIST_ALL, pn, pageSize, params);
    }

    @Override
    public List<M> next(PK pk, int pn, int pageSize)
    {
        if (pk == null)
        {
            return listAll(pn, pageSize);
        }

        Map<String, Object> params = new HashMap<String, Object>();
        params.put(pkName, pk);
        return list(HQL_OPTIMIZE_NEXT_LIST_ALL, pn, pageSize, params);
    }

    @Override
    public boolean exists(PK id)
    {
        return get(id) != null;
    }

    @Override
    public void flush()
    {
        getSession().flush();
    }

    @Override
    public void clear()
    {
        getSession().clear();
    }

    @Override
    public int count(String hql, Object... paramList)
    {
        Query query = getSession().createQuery(hql);
        setParameters(query, paramList);
        Object obj = query.uniqueResult();
        return obj != null ? ((Long) obj).intValue() : 0;
    }

    public void setParameters(Query query, Object[] paramlist)
    {
        if (paramlist != null && paramlist.length > 0)
        {
            for (int i = 0, len = paramlist.length; i < len; ++i)
            {
                if (paramlist[i] instanceof Date)
                {
                    query.setTimestamp(i, (Date) paramlist[i]);
                }
                else
                {
                    query.setParameter(i, paramlist[i]);
                }
            }
        }
    }

    @Override
    public boolean isUnique(M entity, String... uniquePropertyNames)
    {
        if (null == uniquePropertyNames)
        {
            throw new RuntimeException("uniquePropertyNames is null");
        }
        // 获取主键名

        StringBuilder sb = new StringBuilder("select count(").append(pkName).append(") from ").append(
                entityClass.getName()).append(" where 1=1 ");
        Map<String, Object> map = new HashMap<String, Object>();
        try
        {
            // 循环加入唯一列
            for (String name : uniquePropertyNames)
            {
                sb.append(" and ").append(name).append("=:").append(name);
                map.put(name, PropertyUtils.getProperty(entity, name));
            }
            // 以下代码为了如果是update的情况,排除entity自身.
            // 取得entity的主键值
            Object id = PropertyUtils.getProperty(entity, pkName);
            // 如果id!=null,说明对象已存在,该操作为update,加入排除自身的判断
            if (id != null)
            {
                sb.append(" and ").append(pkName).append("<>:").append(pkName);
                map.put(pkName, id);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return false;
        }
        return count(sb.toString(), map) == 0;

    }

    @Override
    public int count(String hql, Map<String, Object> params)
    {
        Query query = getSession().createQuery(hql);
        setParameters(query, params);
        Object obj = query.uniqueResult();
        return obj != null ? ((Long) obj).intValue() : 0;
    }

    @Override
    public <T> T read(String hql, Map<String, Object> params)
    {
        Query query = getSession().createQuery(hql);
        setParameters(query, params);
        return (T) query.uniqueResult();
    }

    @Override
    public <T> List<T> list(String hql)
    {
        return list(hql, -1, -1);
    }

    @Override
    public <T> List<T> list(String hql, int pn, int pageSize)
    {
        return list(hql, pn, pageSize, null, null, null);
    }

    @Override
    public <T> List<T> list(String hql, int pn, int pageSize, Map<String, Object> params)
    {
        return list(hql, pn, pageSize, pkName, DEFAULT_SORT_ORDER, params);
    }

    @Override
    public <T> List<T> list(String hql, int pn, int pageSize, String sort, String sortOrder)
    {
        return list(hql, pn, pageSize, pkName, DEFAULT_SORT_ORDER, null);
    }

    @Override
    public <T> List<T> list(String hql, int pn, int pageSize, String sort, String sortOrder, Map<String, Object> params)
    {
        sort = StringUtils.isBlank(sort) ? pkName : sort;
        sortOrder = DEFAULT_SORT_ORDER.equalsIgnoreCase(sortOrder) ? SORT_ORDER_DESC : SORT_ORDER_ASC;

        hql += " order by " + sort + " " + sortOrder;

        Query query = getSession().createQuery(hql);
        setParameters(query, params);
        if (pn > -1 && pageSize > -1)
        {
            query.setMaxResults(pageSize);
            int start = (pn - 1) * pageSize;
            if (start != 0)
            {
                query.setFirstResult(start);
            }
        }
        if (pn < 0)
        {
            query.setFirstResult(0);
        }
        return query.list();
    }

    public void setParameters(Query query, Map<String, Object> params)
    {
        if (MapUtils.isEmpty(params))
        {
            return;
        }

        for (Map.Entry<String, Object> entry : params.entrySet())
        {
            query.setParameter(entry.getKey(), entry.getValue());
        }
    }

    public Session getSession()
    {
        //事务必须是开启的(Required)，否则获取不到
        return sessionFactory.getCurrentSession();
    }
}
